'''
	Script used to install Xbox game artwork.
	v3.1
'''
import datetime
import io
import re
import requests
import traceback
import zipfile
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
from binascii import unhexlify
from BeautifulSoup import *
from difflib import SequenceMatcher as Fuzzing
from itertools import islice
from os import getcwd, listdir, makedirs, rename, remove, stat, walk
from os.path import basename, isdir, isfile, join, exists
from shutil import copy2, copytree, rmtree
from struct import unpack, calcsize
from time import sleep, clock
from xbmc import executebuiltin, getInfoLabel
from xbmcgui import Dialog, DialogProgress

# extract_xbeinfo
def extract_xbeinfo(FileName):
	xbe_title = u''
	if FileName.lower().endswith('.xbe'):
		# Read only 1024 bytes for the header, faster than reading the whole file.
		with open(FileName, 'rb', buffering=1024) as xbe:
			header = xbe.read(1024)

		# Base certificate start
		loadAddr = unpack('<L', header[0x104:0x108])[0]
		certLoc = unpack('<L', header[0x118:0x11C])[0]
		base_offset = certLoc - loadAddr

		# Extract TitleID (4 bytes)
		id_offset = base_offset + 0x0008
		title_id_raw = unpack('<L', header[id_offset:id_offset + 4])[0]
		title_id = '{:08X}'.format(title_id_raw)

		# Extract Title (80 bytes)
		title_offset = base_offset + 0x000C
		title_block_size = calcsize('40H')
		chars = []
		for dta in unpack('40H', header[title_offset:title_offset + title_block_size]):
			if dta:
				try:
					chars.append(unichr(dta))
				except Exception as error:
					print('Error processing Unicode code {}: {}'.format(dta, error))
		xbe_title = ''.join(chars).strip()
		xbe_title = xbe_title.encode('utf-16le').decode('utf-16le', 'ignore').rstrip(chr(0))
		xbe_title = ''.join(c if ord(c) < 128 else '' for c in xbe_title) # Fix TM and other stupid no ASCII characters	try:
		xbe_title = xbe_title.encode('utf-8') or 'Invalid Name'

		# Extract Region (4 bytes)
		# region_offset = base_offset + 0x00A0
		# region_data = unpack('L', header[region_offset:region_offset + 4])[0]
		# game_region = FULL_GAME_REGIONS.get(region_data, 'Unknown Region ({})'.format(hex(region_data)))
		# short_region_code = SHORT_REGION_CODES.get(region_data, 'u')

		XbeDta = u'{}|{}'.format(xbe_title, str(hex(title_id_raw)[2:10]).upper().zfill(8))
	else:
		with open(FileName, 'r', encoding='utf-8') as f:
			XbeDta = u'{}|{}'.format(xbe_title, f.read()[:8].upper().strip().zfill(8))

	return XbeDta.encode('utf-8')

def strip_trailing_parens(name):
	return re.sub(r'\s*\([^)]*\)$', '', name).strip()

def extract(zip,path):
	with zipfile.ZipFile(zip, 'r') as zip:
		zip.extractall(path)

def download(url, dest, filename):
	zip_data = ''
	timeout_count = 0
	url = 'https://www.xbmc4gamers.co.uk/X4G-XEmu/artwork/{}'.format(requests.utils.quote(url))
	
	# XBMC4Gamers.co.uk attempt time out if request is blocked
	timeout = 10
	max_retries = 3
	
	for _ in range(max_retries):
		if pDialog.iscanceled():
			break
		
		try:
			response = requests.Session().get(
				url,
				headers={"User-Agent": "Mozilla/5.0"},
				stream=True,
				verify=False,
				timeout=timeout
			)
			
			if str(response.status_code).startswith('2'):
				if '.zip' in url:
					pDialog.update((CountList * 100) / Total_Game_Folders, filename, Label_12)
				zip_data = save_response_content(response, dest, filename)
				if zip_data:
					break
			
			if str(response.status_code).startswith('404'):
				if getInfoLabel('Skin.HasSetting(ArtworkInstallerLogging)'):
					print "\nERROR: Not found\n\tType: {}".format(dest)
				break
			
			if str(response.status_code).startswith('429'):
				timeout_count = timeout
				for _ in range(timeout):
					if pDialog.iscanceled():
						break
					if '.zip' in url:
						pDialog.update((CountList * 100) / Total_Game_Folders, filename, 'Retrying in: ' + str(timeout_count))
					time.sleep(1)
					timeout_count -= 1
			
			timeout += 5
		
		except requests.exceptions.Timeout:
			print "\nERROR: Timeout\n\tRetrying..."
			timeout += 5
		
		except requests.exceptions.RequestException as error:
			print "\nERROR: {}\n\tRetrying...".format(error)
			timeout += 5
	
	return zip_data

def save_response_content(Response,Dest,Filename):
	chunk_size = 256 * 1024
	zipData = io.BytesIO()
	for chunk in Response.iter_content(chunk_size):
		if chunk:  # filter out keep-alive new chunks
			zipData.write(chunk)

	if Filename == "info.txt":
		with open(Dest,'wb') as f:
			f.write(zipData.getvalue())
	
	if zipData and getInfoLabel('Skin.HasSetting(ArtworkInstallerLogging)'):
		print ("\nDownloaded:"
				"\n	Type:	{}".format(Dest)
			)

	return zipData

def process_artwork(Game_Directory,ArtworkFolder):
		Source_Poster = join(Game_Directory,'_resources\\artwork\\poster.jpg')
		Source_Thumb = join(Game_Directory,'_resources\\artwork\\thumb.jpg')
		Source_Fanart = join(Game_Directory,'_resources\\artwork\\fanart.jpg')
		Source_Alt_Synopsis = join(Game_Directory,'_resources\\artwork\\alt_synopsis.jpg')
		Source_Video = join(Game_Directory,'_resources\\media\\preview.mp4')
		Game_TBN = join(Game_Directory,'default.tbn')
		Game_Thumb = join(Game_Directory,'icon.png')
		Game_Fanart = join(Game_Directory,'fanart.jpg')
		_Resource = ArtworkFolder+'/_resources.zip'
		_Video = ArtworkFolder+'/_video.zip'
		_Fanart = ArtworkFolder+'/_fanart.zip'
		zipData = ''
		
		if Skip and isfile(Source_Poster) and isfile(Source_Thumb) and isfile(Source_Fanart) and isfile(Source_Alt_Synopsis):
			pDialog.update((CountList * 100) / Total_Game_Folders,Game_Folder.upper(),Label_7)

			if not isfile(Game_TBN) or not isfile(Game_Thumb) or not isfile(Game_Fanart):
				pDialog.update((CountList * 100) / Total_Game_Folders,Game_Folder.upper(),Label_7)
				if isfile(Source_Poster): copy2(Source_Poster,Game_TBN)
				if isfile(Source_Thumb): copy2(Source_Thumb,Game_Thumb)
				if isfile(Source_Fanart): copy2(Source_Fanart,Game_Fanart)
			
			if Video_Install and not isfile(Source_Video):
				zipData = download(_Video,'Preview Video',Game_Folder.upper())
				
				if zipData:
					pDialog.update((CountList * 100) / Total_Game_Folders,Game_Folder.upper(),Label_10)
					extract(zipData,Game_Directory)
			
			if Large_Fanart:
				zipData = download(_Fanart,'Larger Fanart',Game_Folder.upper())
				
				if zipData:
					pDialog.update((CountList * 100) / Total_Game_Folders,Game_Folder.upper(),Label_10)
					extract(zipData,Game_Directory)
		else:
			remove_files_and_folders(Game_Directory)
			
			zipData = download(_Resource,'Artwork',Game_Folder.upper())
			
			if zipData:
				extract(zipData,Game_Directory)
				if isfile(Source_Poster): copy2(Source_Poster,Game_TBN)
				if isfile(Source_Thumb): copy2(Source_Thumb,Game_Thumb)
				if isfile(Source_Fanart): copy2(Source_Fanart,Game_Fanart)
			
			if Video_Install:
				zipData = download(_Video,'Preview Video',Game_Folder.upper())
				
				if zipData:
					extract(zipData,Game_Directory)
			
			if Large_Fanart:
				zipData = download(_Fanart,'Larger Fanart',Game_Folder.upper())
				
				if zipData:
					extract(zipData,Game_Directory)
			
			pDialog.update((CountList * 100) / Total_Game_Folders,Game_Folder.upper(),Label_10)

def remove_files_and_folders(Sub_Folder):
	To_Remove = [
		'_resources',
		'default.tbn',
		'fanart.jpg',
		'icon.jpg',
		'icon.png'
	]
	
	for Path in To_Remove:
		Full_Path = join(Sub_Folder, Path)
		if isdir(Full_Path):
			rmtree(Full_Path)
		elif isfile(Full_Path):
			remove(Full_Path)

def sort_key(String):
	Num = re.findall(r'\d+', String)
	if Num:
		return int(Num[0])
	return String

def call_exception(Exception):
	exception_type = type(error).__name__
	exception_message = str(error)
	stack_trace = traceback.format_exc().splitlines()

	exceptionarray = []
	exceptionarray.append("An error of type {} occurred. Arguments:\n{}\n".format(exception_type, exception_message))
	exceptionarray.append("Stack trace:")
	for line in stack_trace:
		exceptionarray.append("\t" + line)
	
	dialog.textviewer('ERROR', "\n".join(exceptionarray))

def print_match(Var1, Var2, Var3, Var4, Var5, Var6, Var7):
	if Logging:
		print (
			"\n\n{}\n"
			"\n\t   Folder:\t{}"
			"\n\t     Info:\t{}"
			"\n\t     Path:\t{}"
			"\n\t    Ratio:\t{:.0f}%"
			"\n\t     Type:\t{}"
			"{}".format(Var1,Var2.lower(),Var3,Var4,Var5,Var6,Var7
			)
		)

def internet_check():
	try:
		_ = requests.head('https://www.xbmc4gamers.co.uk/', timeout=3, verify=False)
	except requests.ConnectionError:
		raise Exception("No Interweb access. Please check your network settings")

def wf_levenshtein(St1, St2):
	# This isn't used, source from: https://github.com/toastdriven/pylev/blob/main/pylev/wf.py#L10
	"""
	Calculates the Levenshtein distance between two strings.

	This version uses the Wagner-Fischer algorithm.

	Usage::

		>>> wf_levenshtein('kitten', 'sitting')
		3
		>>> wf_levenshtein('kitten', 'kitten')
		0
		>>> wf_levenshtein('', '')
		0

	"""
	len_1 = len(St1) + 1
	len_2 = len(St2) + 1

	d = [0] * (len_1 * len_2)

	for i in range(len_1):
		d[i] = i
	for j in range(len_2):
		d[j * len_1] = j

	for j in range(1, len_2):
		for i in range(1, len_1):
			if St1[i - 1] == St2[j - 1]:
				d[i + j * len_1] = d[i - 1 + (j - 1) * len_1]
			else:
				d[i + j * len_1] = min(
					d[i - 1 + j * len_1] + 1,  # deletion
					d[i + (j - 1) * len_1] + 1,  # insertion
					d[i - 1 + (j - 1) * len_1] + 1,  # substitution
				)

	return d[-1]

def levenshtein_ratio(St1, St2):
	l1, l2 = len(St1),len(St2)
	ratio = float( l1 + l2 - int(wf_levenshtein(St1, St2)) ) / (l1 + l2)
	return int(100 * ratio)


if __name__ == "__main__":
	# print("Arguments:", sys.argv)
	args = sys.argv[1:] + [0]*6
	arg_standalone, Skip, Video_Install, Large_Fanart, Try_Folder_Names, Logging = [int(arg) for arg in args[:6]]
	custompath_args_str = args[6:]
	Custom_Game_Directories = [arg for arg in custompath_args_str if arg != 0]

	dialog = Dialog()
	pDialog = DialogProgress()
	executebuiltin('ActivateWindow(1100)')
	executebuiltin('Skin.SetBool(MenuVisible)')
	executebuiltin('Skin.Reset(DisableCancel)')
	# Set Vars
	global Game_Folder
	Working_Directory = getcwd()
	ArtworkMasterList = join(Working_Directory, 'Artwork Info.txt')
	Total_Memory = getInfoLabel('System.memory(total_no_mb)')
	Total_Artwork_Folders = 0
	Total_Game_Folders = 0
	pDialog.update(0,"","") # used to make sure the progress bar is 0
	missingartwork = 0
	cancellation = 0
	cancelled = 0
	CountList = 0
	Error = 0
	ArtworkFile = ""
	duration = ""
	xbe_bytes	= unhexlify('584245480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000007801010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
	synopsis = '<synopsis>\n<sourcename>%s</sourcename>\n<foldername>%s</foldername>\n<title>%s</title>\n<developer></developer>\n<publisher></publisher>\n<features_general></features_general>\n<features_online/>\n<esrb></esrb>\n<esrb_descriptors></esrb_descriptors>\n<genre></genre>\n<release_date></release_date>\n<rating></rating>\n<platform>Xbox</platform>\n<exclusive></exclusive>\n<titleid>%s</titleid>\n<overview></overview>\n</synopsis>'
	# Labels
	if arg_standalone:
		Label_1 = ""
	else:
		Label_1 = "Artwork Installer Online"
	Label_4 = "Start Artwork Installation?"
	Label_5 = "Currently we have artwork for %s Titles"
	Label_6 = "Checking"
	Label_7 = "Fixing Artwork"
	Label_9 = "ERROR"
	Label_10 = "Installing"
	Label_11 = "Skipping"
	Label_12 = "Downloading"
	Label_13 = "Installation cancelled"
	Label_14 = "Artwork installed"
	Label_18 = "Downloading latest[CR][B]Artwork Info.txt[/B]"
	Label_19 = "Initialising please wait..."
	Label_21 = "Creating missing artwork.zip"

	Game_Directories = []
	Directories_E = []
	Directories_F = []
	Directories_G = []

	if not xbmc.getInfoLabel('system.totalspace(E)').split(' ',2)[1] == "Unavailable": Directories_E = [ "E:\\Demos\\", "E:\\Emulators\\", "E:\\Games\\", "E:\\Homebrew\\", "E:\\Ports\\", "E:\\Apps\\", "E:\\Applications\\" ]
	if not xbmc.getInfoLabel('system.totalspace(F)').split(' ',2)[1] == "Unavailable": Directories_F = [ "F:\\Demos\\", "F:\\Emulators\\", "F:\\Games\\", "F:\\Homebrew\\", "F:\\Ports\\", "F:\\Apps\\", "F:\\Applications\\" ]
	if not xbmc.getInfoLabel('system.totalspace(G)').split(' ',2)[1] == "Unavailable": Directories_G = [ "G:\\Demos\\", "G:\\Emulators\\", "G:\\Games\\", "G:\\Homebrew\\", "G:\\Ports\\", "G:\\Apps\\", "G:\\Applications\\" ]
	
	if arg_standalone:
		Custom_Game_Directories = [ xbmc.getInfoLabel('Skin.String(CustomPath1Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath2Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath3Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath4Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath5Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath6Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath7Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath8Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath9Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath10Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath11Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath12Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath13Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath14Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath15Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath16Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath17Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath18Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath19Selected)'), xbmc.getInfoLabel('Skin.String(CustomPath20Selected)')]

	# Check if sources exist and if they do they aren't empty
	for dir_check in filter(None, Directories_E + Directories_F + Directories_G + Custom_Game_Directories):
		if exists(dir_check) and listdir(dir_check):
			Game_Directories.append(dir_check)

	try: # check if you have Insignia DNS set and a second DNS as it's required
		if getInfoLabel('Network.DNS1Address') == "46.101.64.175" and getInfoLabel('Network.DNS2Address') == "0.0.0.0":
			raise Exception("Insignia DNS detected. You have not set a secondary DNS.")
		try:
			internet_check()
	#
	#	Start of main code.
	#
			try:
				# check time stamp so we don't download it every single time.
				executebuiltin('Skin.SetString(DisableCancel,1)')
				start_time = datetime.datetime.now()
				try:
					check_timestamp = datetime.datetime.fromtimestamp(stat(ArtworkMasterList).st_mtime)
					duration = start_time - check_timestamp
				except: duration = "bypass"
				if duration == "bypass" or duration > datetime.timedelta(minutes=10):
					# pDialog.create(Label_1,"",Label_18)
					download('info.txt',ArtworkMasterList,'info.txt')

				with open(ArtworkMasterList, 'r') as InputArtworkFile:
					ArtworkTotal = InputArtworkFile.readline().split(' = ')[1].strip()

				executebuiltin('Skin.Reset(DisableCancel)')
				xbmc.executebuiltin('Dialog.Close(1100)')

				if Error == 0:
					
					if dialog.yesno(Label_1,Label_4,Label_5 % (str(ArtworkTotal)),'',xbmc.getLocalizedString(106),xbmc.getLocalizedString(107)):
						if int(Total_Memory) > 64 and getInfoLabel('Skin.HasSetting(ArtworkInstallerFanart)'):
							Large_Fanart = 1
						else:
							executebuiltin('Skin.Reset(ArtworkInstallerFanart)')
						if getInfoLabel('Skin.HasSetting(ArtworkInstallerVideo)'):
							Video_Install = 1
						if getInfoLabel('Skin.HasSetting(ArtworkInstallerSkip)'):
							Skip = 1
						if getInfoLabel('Skin.HasSetting(ArtworkInstallerAddChecks)'):
							Try_Folder_Names = 1
						if getInfoLabel('Skin.HasSetting(ArtworkInstallerLogging)'):
							Logging = 1

						pDialog.create(Label_1,Label_19)
						pDialog.update(0)
						# Grab total game folder count
						for Game_Sub_Folders in Game_Directories:
							if isdir(Game_Sub_Folders):
								for Game_Sub_Sub_Folders in listdir(Game_Sub_Folders):
									if isfile(join(Game_Sub_Folders,Game_Sub_Sub_Folders,'default.xbe')):
										Total_Game_Folders = Total_Game_Folders+1

						CountList = 0
						IgnoreList = []
						for Game_Dirs in Game_Directories:
							if isdir(Game_Dirs):
								for Game_Folder in sorted([f for f in listdir(Game_Dirs)]):
									# try:
										# Game_Folder_Full_Path = join(Game_Dirs, Game_Folder)
										# Game_Folder_Full_Path_Alt = join(Game_Dirs, Game_Folder.title())
										# if Game_Folder_Full_Path != Game_Folder_Full_Path_Alt:
											# tempname = Game_Folder_Full_Path_Alt[:-1]+"_"
											# if not isfile(tempname):
												# rename(Game_Folder_Full_Path_Alt, tempname)
												# rename(tempname, Game_Folder_Full_Path_Alt)
									# except: pass
									
									if not pDialog.iscanceled() and Error == 0:
										if isdir(join(Game_Dirs, Game_Folder)):
											Game_Directory = join(Game_Dirs, Game_Folder)
											XBEFile = join(Game_Directory, "default.xbe")
											Source_Poster = join(Game_Directory,'_resources\\artwork\\poster.jpg')
											Source_Thumb = join(Game_Directory,'_resources\\artwork\\thumb.jpg')
											Source_Alt_Synopsis = join(Game_Directory,'_resources\\artwork\\alt_synopsis.jpg')
											
											if isfile(XBEFile):
												if not Skip or not isfile(Source_Poster) or not isfile(Source_Thumb) or not isfile(Source_Alt_Synopsis):
													AltCheck = 0
													Found = 0
													AltChecklb = ""
													min_ratio = 85
													max_ratio = 100
													CountList = CountList+1
													pDialog.update((CountList * 100) / Total_Game_Folders, "{}".format(basename(Game_Directory).upper()), Label_6)
													
													try:
														# this is set to the default.xbe because some of the main game xbe files don't have names. Some games use default.xbe as the loader to game.xbe
														XBEInfo = extract_xbeinfo(XBEFile).split('|')
														XBETitle =  XBEInfo[0].lstrip(' ')
														if isfile(join(Game_Directory, "game.xbe")):
															if "Homebrew" in Game_Directory:
																pass
															elif "Ports" in Game_Directory:
																pass
															elif "Apps" in Game_Directory:
																pass
															elif "Applications" in Game_Directory:
																pass
															else:
																XBEInfo = extract_xbeinfo( join(Game_Directory, "game.xbe")).split('|')
														if isfile(join(Game_Directory, "Override_TitleID.bin")):
															with open(join(Game_Directory, "Override_TitleID.bin"), "r") as overridefile:
																XBEID = overridefile.readline().lower()
														else:
															XBEID = XBEInfo[1].lower()
													except:
														pDialog.close()
														Error = 1
														dialog.ok(Label_9,"",Game_Folder+"[CR]This games Default.xbe is corrupt or damaged.")
													
													with open(ArtworkMasterList, 'r') as InputArtworkFile:
														ArtworkFileMemory = ''.join(InputArtworkFile.readlines()[1:]) # skip line 1
													TitleID_Found = re.findall(r'^'+re.escape(XBEID)+'.*$', ArtworkFileMemory, re.MULTILINE)
													
													Sanitized_Game_Folder = strip_trailing_parens(Game_Folder)
													Sanitized_XBETitle = strip_trailing_parens(XBETitle)

													# if titleid/s is found for the current xbe parse them.
													for titles in TitleID_Found:
														TitlesID, TitlesFolder, TitlesBase = titles.split('|')[0], titles.split('|')[1].replace('\\','/').rstrip(), basename(titles.split('|')[1].replace('\\','/')).rstrip()
														
														# this uses difflib as levenshtein_ratio didn't do much better.
														Fuzzer_1 = Fuzzing(None, TitlesBase.lower(), Sanitized_Game_Folder.lower()).ratio() * 100
														Fuzzer_2 = Fuzzing(None, TitlesBase.lower(), Sanitized_XBETitle.lower()).ratio() * 100
														
														if Found == 0:
															if min_ratio <= int("{:.0f}".format(Fuzzer_1)) <= max_ratio:
																Found = 1
																print_match("Game folder Match",Game_Folder.lower(),TitlesBase,Game_Directory,Fuzzer_1,"Game folder  >  Artwork folder","")
																process_artwork(Game_Directory,TitlesFolder)
															
															elif min_ratio <= int("{:.0f}".format(Fuzzer_2)) <= max_ratio:
																Found = 1
																print_match("XBE Title Match",Game_Folder.lower(),TitlesBase,Game_Directory,Fuzzer_2,"XBE Title  >  Artwork folder","")
																process_artwork(Game_Directory,TitlesFolder)
															
															elif len(TitleID_Found) == 1: # processes only the first instance for games that share TitleIDs force a folder/title match
																# if this setting is enabled double check whats found
																if Try_Folder_Names:
																	AltCheck = 1
																	print_match("TitleID Match",Game_Folder.lower(),TitlesBase,Game_Directory,100,"XBE TitleID  >  Stored TitleID",'\n\t AltCheck:\tTrue')
																else:
																	print_match("TitleID Match",Game_Folder.lower(),TitlesBase,Game_Directory,100,"XBE TitleID  >  Stored TitleID","")
																	process_artwork(Game_Directory,TitlesFolder)
																# if there are more than 1 titleid found repeat as the folder or title name may match instead
																if len(TitleID_Found) > 1:
																	Found = 0

													# will check against the folder/title names of xbes.
													# will also check if enabled xbes match the found ids in the artwork info.txt file. It's just a additional check.
													if isfile(XBEFile) and AltCheck or isfile(XBEFile) and not Found and Try_Folder_Names:
														for lines in sorted(ArtworkFileMemory.split('\n'), key=sort_key):
															if lines.strip():
																ArtworkFolder, ArtworkFolderBase = lines.split('|')[1].replace('\\','/').rstrip(), basename(lines.split('|')[1].replace('\\','/')).rstrip()
																if ArtworkFolderBase not in IgnoreList:
																	# this uses difflib as levenshtein_ratio didn't do much better.
																	Fuzzer_3 = Fuzzing(None, ArtworkFolderBase.lower(), Sanitized_Game_Folder.lower()).ratio() * 100
																	Fuzzer_4 = Fuzzing(None, ArtworkFolderBase.lower(), Sanitized_XBETitle.lower()).ratio() * 100
																	
																	if min_ratio <= int("{:.0f}".format(Fuzzer_3)) <= max_ratio:
																		print_match("Additional Check For Match",Game_Folder.lower(),ArtworkFolderBase,Game_Directory,Fuzzer_3,"Game folder  >  Artwork folder","")
																		# do an additional check if the titleid is flagged, when try folder mode is enabled
																		# if the check is different (better) than the name tied to the titleid use it if not use previous match it
																		if AltCheck:
																			if Fuzzer_3 > Fuzzer_1:
																				if not Skip:
																					remove_files_and_folders(Game_Directory)
																				process_artwork(Game_Directory,ArtworkFolder)
																			else:
																				process_artwork(Game_Directory,TitlesFolder)
																		else:
																			process_artwork(Game_Directory,ArtworkFolder)
																		# add last detected folder name to ignore list
																		IgnoreList.append(ArtworkFolderBase)
																		break
																	
																	elif min_ratio <= int("{:.0f}".format(Fuzzer_4)) <= max_ratio:
																		print_match("Additional Check For Match",Game_Folder.lower(),ArtworkFolderBase,Game_Directory,Fuzzer_4,"XBE Title  >  Artwork folder","")
																		# do an additional check if the titleid is flagged, when try folder mode is enabled
																		# if the check is different (better) than the name tied to the titleid use it if not use previous match it
																		if AltCheck:
																			if Fuzzer_4 > Fuzzer_2:
																				if not Skip:
																					remove_files_and_folders(Game_Directory)
																				process_artwork(Game_Directory,ArtworkFolder)
																			else:
																				process_artwork(Game_Directory,TitlesFolder)
																		else:
																			process_artwork(Game_Directory,ArtworkFolder)
																		# add last detected folder name to ignore list
																		IgnoreList.append(ArtworkFolderBase)
																		break
														# using the altcheck, if all of the above fail fallback to titleid match
														if AltCheck and int("{:.0f}".format(Fuzzer_3)) < min_ratio and int("{:.0f}".format(Fuzzer_4)) <= min_ratio:
															print ("\nFallback Failed Alt Checks:"
																"\n\t   Folder:\t{}"
																"\n\t     Info:\t{}"
																"\n\t    Ratio:\t{}% - {}%"
																"\n\t     Type:\tTitleID".format(Sanitized_Game_Folder, TitlesBase, int("{:.0f}".format(Fuzzer_3)), int("{:.0f}".format(Fuzzer_4)))
															)
															process_artwork(Game_Directory,TitlesFolder)
													
													if len(TitleID_Found) == 0:
														missingartwork = 1
														if not isdir(join('Y:/missing artwork',Game_Folder)):
															makedirs(join('Y:/missing artwork',Game_Folder))

														if XBETitle == "": XBETitle = Game_Folder
														try:
															with open(join('Y:/missing artwork',Game_Folder,'Game_Inf.ini'), 'w') as MissingArtworkFileini:
																MissingArtworkFileini.write('[xbe_title]\n'+XBETitle+'\n[folder_name]\n'+Game_Folder+'\n[title_id]\n'+XBEID)
															with open(join('Y:/missing artwork',Game_Folder,'default.xml'), 'w') as MissingArtworkFilexml:
																MissingArtworkFilexml.write(synopsis % (XBETitle,Game_Folder,XBETitle,XBEID))
															with open(join('Y:/missing artwork',Game_Folder,'default.xbe'), 'wb') as MissingArtworkFile:
																XBEID = XBEID[-2:]+XBEID[-4:-2]+XBEID[-6:-4]+XBEID[-8:-6] # hacky way of flipping the bytes
																XBETitle = XBETitle.encode('utf-16').ljust(82,'\x00').encode('hex')[4:]
																MissingArtworkFile.write(xbe_bytes+unhexlify(XBEID)+unhexlify(XBETitle))
														except: pass
												else:
													CountList = CountList+1
													pDialog.update((CountList * 100) / Total_Game_Folders, "{}".format(basename(Game_Directory).upper()), Label_11)
									else:
										if Error:
											pDialog.close()
										else:
											cancellation = 1
						
						if cancellation == 1:
							pDialog.close()
							dialog.ok(Label_1,Label_13)

						if cancellation == 0 and not Error == 1:
							if missingartwork == 1: 
								if isdir('Y:/missing artwork'):
									if not isdir('Q:\\missing artwork'): makedirs('Q:\\missing artwork')
									zip_output = join('Q:\\missing artwork\\missing artwork.zip')
									Total_Game_Folders = len(listdir('Y:\\missing artwork'))
									CountList = 0
									zip = zipfile.ZipFile(zip_output, 'w')
									for root, dirs, files in walk('Y:\\missing artwork'):
										for file in files:
											pDialog.update((CountList * 100) / Total_Game_Folders,Label_21,"",'This can take some time, please be patient.')
											zip.write(join(root, file))
										CountList = CountList+1
									zip.close()
									pDialog.close()
									dialog.ok(Label_1,Label_14)
							else:
								pDialog.close()
								dialog.ok(Label_1,"",Label_14)
							pDialog.close()

			except Exception as error:
				pDialog.close()
				call_exception(error)
		
		except Exception as networkissue:
			xbmc.executebuiltin('Dialog.Close(1100)')
			dialog.textviewer('NETWORK CHECK FAILED','''No Internet access or www.xbmc4gamers.co.uk is down.
			
Please check your network settings or make sure you have an Ethernet cable/WIFI adaptor connected to a modem/PC (sharing) or router.

Internet access is required for this version of the Artwork Installer.''')

	except Exception as dnsissue:
		xbmc.executebuiltin('Dialog.Close(1100)')
		dialog.textviewer('INSIGNIA DNS DETECTED', '''You do not have a secondary DNS set.
		
The Artwork Installer will not function without it being set.
		
You can fix this by setting the DNS settings under the Microsoft dashboard, XBMC4Gamers/XBMC-Emustation network settings, or UnleashX network settings.

When you have done this rerun the Artwork Installer again and you should be good :D''')

executebuiltin('Skin.Reset(MenuVisible)')